/******************************************************************************* * Copyright (c) 2006, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Andrey Loskutov <loskutov@gmx.de> - Bug 436225 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 * Fabio Zadrozny <fabiofz at gmail dot com> - Bug 459833 *******************************************************************************/ package org.eclipse.ui.internal.services; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.services.AbstractServiceFactory; import org.eclipse.ui.services.IDisposable; import org.eclipse.ui.services.IServiceLocator; /** * @since 3.2 * */ public final class ServiceLocator implements IDisposable, INestable, IServiceLocator { boolean activated = false; private static class ParentLocator implements IServiceLocator { private IServiceLocator locator; private Class<?> key; public ParentLocator(IServiceLocator parent, Class<?> serviceInterface) { locator = parent; key = serviceInterface; } @SuppressWarnings("unchecked") @Override public <T> T getService(Class<T> api) { if (key.equals(api)) { return (T) locator.getService(key); } return null; } @Override public boolean hasService(Class<?> api) { if (key.equals(api)) { return true; } return false; } } private final AbstractServiceFactory factory; /** * The parent for this service locator. If a service can't be found in this * locator, then the parent is asked. This value may be <code>null</code> if * there is no parent. */ private final IServiceLocator parent; private volatile boolean disposed; private IDisposable owner; private volatile IEclipseContext e4Context; private final Map<Class<?>, Object> services = new ConcurrentHashMap<>(); /** * Constructs a service locator with no parent. */ public ServiceLocator() { this(null, null, null); } /** * Constructs a service locator with the given parent. * * @param parent * The parent for this service locator; this value may be * <code>null</code>. * @param factory * a local factory that can provide services at this level * @param owner */ public ServiceLocator(final IServiceLocator parent, AbstractServiceFactory factory, IDisposable owner) { this.parent = parent; this.factory = factory; this.owner = owner; } @Override public final void activate() { activated = true; for (Object service : services.values()) { if (!(service instanceof INestable)) { continue; } SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { ((INestable) service).activate(); } @Override public void handleException(Throwable ex) { WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.ERROR, "Error while activating: " + service, ex)); //$NON-NLS-1$ } }); } } @Override public final void deactivate() { activated = false; for (Object service : services.values()) { if (!(service instanceof INestable)) { continue; } SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { ((INestable) service).deactivate(); } @Override public void handleException(Throwable ex) { WorkbenchPlugin .log(StatusUtil.newStatus(IStatus.ERROR, "Error while deactivating: " + service, ex)); //$NON-NLS-1$ } }); } } @Override public final void dispose() { disposeServices(); if (services.size() > 0) { // If someone registered during shutdown, dispose of it too. // See: Bug 459833 - ConcurrentModificationException in // ServiceLocator.dispose disposeServices(); } // Check if there was some other leftover and warn about it. if (services.size() > 0) { WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.WARNING, String.format( "Services: %s register themselves while disposing (skipping dispose of such services).", //$NON-NLS-1$ services), null)); } services.clear(); disposed = true; e4Context = null; owner = null; } private void disposeServices() { Iterator<Entry<Class<?>, Object>> iterator = services.entrySet().iterator(); while (iterator.hasNext()) { Entry<Class<?>, Object> entry = iterator.next(); if (entry.getValue() instanceof IDisposable) { IDisposable iDisposable = (IDisposable) entry.getValue(); SafeRunner.run(new ISafeRunnable() { @Override public void run() throws Exception { iDisposable.dispose(); } @Override public void handleException(Throwable ex) { WorkbenchPlugin .log(StatusUtil.newStatus(IStatus.ERROR, "Error while disposing: " + iDisposable.getClass().getName(), ex)); //$NON-NLS-1$ } }); } iterator.remove(); } } @SuppressWarnings("unchecked") @Override public final <T> T getService(final Class<T> key) { IEclipseContext context = e4Context; if (context == null) { return null; } if (IEclipseContext.class.equals(key)) { return (T) context; } Object service = context.get(key.getName()); if (service == null) { // this scenario can happen when we dispose the service locator // after the window has been removed, in that case the window's // context has been destroyed so we should check our own local cache // of services first before checking the registry service = services.get(key); } else if (service == context.getLocal(key.getName())) { // store this service retrieved from the context in the map only if // it is a local service for this context, as otherwise we do not // want to dispose it when this service locator gets disposed registerService(key, service, false); } if (service == null) { // nothing cached, check registry then parent IServiceLocator factoryParent = WorkbenchServiceRegistry.GLOBAL_PARENT; if (parent != null) { factoryParent = new ParentLocator(parent, key); } if (factory != null) { service = factory.create(key, factoryParent, this); } if (service == null) { service = WorkbenchServiceRegistry.getRegistry().getService(key, factoryParent, this); } if (service == null) { service = factoryParent.getService(key); } else { registerService(key, service, true); } } return (T) service; } @Override public final boolean hasService(final Class<?> key) { IEclipseContext context = e4Context; if (context == null) { return false; } return context.containsKey(key.getName()); } /** * Registers a service with this locator. If there is an existing service * matching the same <code>api</code> and it implements {@link IDisposable}, * it will be disposed. * * @param api * This is the interface that the service implements. Must not be * <code>null</code>. * @param service * The service to register. This must be some implementation of * <code>api</code>. This value must not be <code>null</code>. */ public final void registerService(final Class api, final Object service) { registerService(api, service, true); } private void registerService(Class<?> api, Object service, boolean saveInContext) { if (api == null) { throw new NullPointerException("The service key cannot be null"); //$NON-NLS-1$ } if (!api.isInstance(service)) { throw new IllegalArgumentException( "The service does not implement the given interface"); //$NON-NLS-1$ } if (isDisposed()) { IllegalStateException ex = new IllegalStateException("An attempt was made to register service " + service //$NON-NLS-1$ + " with implementation class " + api + " on a disposed service locator"); //$NON-NLS-1$//$NON-NLS-2$ WorkbenchPlugin.log(StatusUtil.newStatus(IStatus.ERROR, ex.getMessage(), ex)); return; } if (service instanceof INestable && activated) { ((INestable) service).activate(); } services.put(api, service); if (saveInContext) { IEclipseContext context = e4Context; if (context == null) { return; } context.set(api.getName(), service); } } /** * @return */ public boolean isDisposed() { return disposed; } /** * Some services that were contributed to this locator are no longer * available (because the plug-in containing the AbstractServiceFactory is * no longer available). Notify the owner of the locator about this. */ public void unregisterServices(String[] serviceNames) { if (owner != null) { owner.dispose(); } } public void setContext(IEclipseContext context) { e4Context = context; } public IEclipseContext getContext() { return e4Context; } }